package org.windowkit.toolkit.widget.date;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.HeadlessException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicArrowButton;
import javax.swing.text.DateFormatter;


/**
 * This class will display a calendar and report back to the creating class on
 * which date, if any, was picked.
 * @author Eric Lundin
 */
public class PopupMenuCalendar extends JPopupMenu {
	private static final long serialVersionUID = -4137830205441809073L;

	private static final double FONT_DIVISOR = 1.25;
	private static final String NEXT_MONTH = "Next Month";
	private static final String PREVIOUS_MONTH = "Previous Month";
	private static final String PREVIOUS_YEAR = "Previous Year";
	private static final String NEXT_YEAR = "Next Year";

	private final int vgap = 1;
	private final int hgap = 1;
	private final String[] dayNames = { "S", "M", "T", "W", "T", "F", "S" };

	private final Calendar calendar;
	private Calendar originalCalendar;
	private JLabel monthName;
	private JLabel yearName;
	private JLabel[] days;
	private JPanel dayPanel;
	private JPanel buttonPanel;
	private JPanel dayNamesPanel;
	private JPanel monthPanel;
	private JPanel yearPanel;
	private JPanel cancelPanel;
	private JPanel calendarDisplayPanel;
	private Spinner spinner;
	private GridLayout dayPanelLayout;

	private BasicArrowButton nextYear;
	private BasicArrowButton prevYear;
	private BasicArrowButton nextMonth;
	private BasicArrowButton prevMonth;

	private SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");
	private DateFormatter formatter = new DateFormatter(format);
	private final DateFormatter monthFormat;
	private final DateFormatter yearFormat;

	private CalendarMouseListener mouseListener;
	private CalendarActionListener actionListener;

	private Color highlightColorBackground;
	private Color highlightColorText;
	private Color normalColorBackground;
	private Color normalColorText;
	private Color dayNamesColorBackground;



	/**
	 * Creates a generic calendar which will start with today's date.
	 *
	 * @throws HeadlessException
	 */
	public PopupMenuCalendar() throws HeadlessException {
		super();
		calendar = new GregorianCalendar(Locale.getDefault());
		monthFormat = new DateFormatter(new SimpleDateFormat("MMMMM"));
		yearFormat = new DateFormatter(new SimpleDateFormat("yyyy"));
		init();

	}

	/**
	 * Creates calendar which will start with the given date.
	 *
	 * @throws HeadlessException
	 */
	public PopupMenuCalendar(final String initialDate, final Spinner spinner)
			throws HeadlessException {
		super("Calendar");
		Date date;
		calendar = new GregorianCalendar();
		originalCalendar = new GregorianCalendar();
		monthFormat = new DateFormatter(new SimpleDateFormat("MMMMM"));
		yearFormat = new DateFormatter(new SimpleDateFormat("yyyy"));
		if (initialDate == null) {
			date = new Date();
		}
		else {
			try {
				date = (Date) formatter.stringToValue(initialDate);
			} catch (ParseException e) {
				date = new Date();
			}
		}
		calendar.setTime(date);
		this.spinner = spinner;
		this.originalCalendar.setTime(date);
		init();

	}

	private void init() {
		BoxLayout layout;
		mouseListener = new CalendarMouseListener();
		actionListener = new CalendarActionListener();
		calendarDisplayPanel = new JPanel();
		layout = new BoxLayout(this, BoxLayout.Y_AXIS);// getContentPane()
		calendarDisplayPanel.setLayout(new BoxLayout(calendarDisplayPanel,
				BoxLayout.Y_AXIS));
		setLayout(layout);

		initializeColors();
		addComponents();
	}

	private void addComponents() {
		add(initYearPanel());
		add(initMonthNamePanel());
		populateDayPanel();
		calendarDisplayPanel.add(initDayNamePanel());
		calendarDisplayPanel.add(dayPanel);
		calendarDisplayPanel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0,
				5));
		add(calendarDisplayPanel);
		add(Box.createVerticalStrut(5));
		setComponentSize();
	}

	private void setComponentSize() {
		dayPanel.getComponent(0).getPreferredSize();
		Dimension nameLabelSize = new Dimension("November".length()
				* monthName.getFont().getSize(), (int) monthName
				.getPreferredSize().getHeight());
		monthName.setPreferredSize(nameLabelSize);
		yearName.setPreferredSize(nameLabelSize);
	}

	private void initializeColors() {
		highlightColorBackground = UIManager
				.getColor("TextField.selectionBackground");
		highlightColorText = UIManager
				.getColor("TextField.selectionForeground");
		normalColorBackground = UIManager.getColor("Panel.background");
		normalColorText = UIManager.getColor("Panel.foreground");
		dayNamesColorBackground = UIManager.getColor("Menu.background");
	}

	private JPanel initYearPanel() {
		yearPanel = new JPanel();
		try {
			yearName = new JLabel(yearFormat.valueToString(calendar.getTime()),
					SwingConstants.CENTER);
			prevYear = new BasicArrowButton(SwingConstants.WEST);
			nextYear = new BasicArrowButton(SwingConstants.EAST);
			prevYear.setActionCommand(PREVIOUS_YEAR);
			nextYear.setActionCommand(NEXT_YEAR);
			prevYear.addActionListener(actionListener);
			nextYear.addActionListener(actionListener);
			yearPanel.add(prevYear);
			yearPanel.add(yearName);
			yearPanel.add(nextYear);
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return yearPanel;
	}

	private void populateDayPanel() {
		int daysInMonth;
		int firstDayOfMonth;
		String labelText;
		if (dayPanel == null) {
			dayPanelLayout = new GridLayout();
			dayPanel = new JPanel();
			dayPanel.setLayout(dayPanelLayout);
		}
		daysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
		firstDayOfMonth = findFirstDayOfMonth() - 1;
		for (int i = 0; i < 42; i++) {
			if (i < firstDayOfMonth || (i >= firstDayOfMonth + daysInMonth)) {
				labelText = "";

			} else {
				labelText = new Integer(i - firstDayOfMonth + 1).toString();
			}
			if (dayPanel.getComponentCount() > i) {
				JPanel currentComp = ((JPanel) dayPanel.getComponent(i));
				if (((JLabel) currentComp.getComponent(0)).getText().equals("")
						&& labelText.length() > 0) {
					currentComp.addMouseListener(mouseListener);
				}
				((JLabel) (((JComponent) dayPanel.getComponent(i))
						.getComponent(0))).setText(labelText);
			} else {
				dayPanel.add(createPanel(labelText, i));
			}
			if (labelText.equals("")) {
				dayPanel.getComponent(i).removeMouseListener(mouseListener);
			}
		}

		JPanel clearPanel = ((JPanel) dayPanel.getComponent(41));
		((JLabel) clearPanel.getComponent(0)).setText("C");
		clearPanel.addMouseListener(mouseListener);
		dayPanelLayout.setColumns(7);
		dayPanelLayout.setRows(6);
		dayPanelLayout.layoutContainer(dayPanel);
		dayPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));

	}

	private JPanel createPanel(final String labelText, final int index) {
		JPanel currentPanel = new JPanel();
		currentPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory
				.createLineBorder(getForeground(), 1), BorderFactory
				.createEmptyBorder(vgap, hgap / 2, vgap, hgap / 2)));
		JLabel currentLabel = new JLabel(labelText, SwingConstants.CENTER);
		divideFont(currentLabel, FONT_DIVISOR);
		currentPanel.add(currentLabel);
		if (labelText.length() > 0) {
			currentPanel.addMouseListener(mouseListener);
		}
		return currentPanel;
	}

	private int findFirstDayOfMonth() {
		int firstDayOfMonth;
		Calendar temp = (Calendar) calendar.clone();
		temp.set(Calendar.DAY_OF_MONTH, 1);
		firstDayOfMonth = temp.get(Calendar.DAY_OF_WEEK);
		return firstDayOfMonth;
	}

	private JPanel initDayNamePanel() {
		JPanel currentPanel;
		JLabel currentLabel;
		dayNamesPanel = new JPanel();
		dayNamesPanel.setLayout(new GridLayout(1, dayNames.length));

		for (String currentDay : dayNames) {
			currentPanel = new JPanel();
			currentLabel = new JLabel(currentDay, SwingConstants.CENTER);
			divideFont(currentLabel, FONT_DIVISOR);
			currentPanel.add(currentLabel);
			currentPanel.setBorder(BorderFactory.createCompoundBorder(
					BorderFactory.createLineBorder(getForeground(), 1),
					BorderFactory.createEmptyBorder(0, hgap, 0, hgap)));
			currentPanel.setBackground(dayNamesColorBackground);
			dayNamesPanel.add(currentPanel);
		}
		dayNamesPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
		return dayNamesPanel;
	}

	/**
	 * @param currentLabel
	 * @param d
	 */
	private void divideFont(final JComponent component, final double d) {
		Font currentFont = component.getFont();
		component.setFont(new Font(currentFont.getName(), currentFont
				.getStyle(), (int) (currentFont.getSize() / d)));

	}

	private JPanel initMonthNamePanel() {
		monthPanel = new JPanel();
		try {
			monthName = new JLabel(monthFormat
					.valueToString(calendar.getTime()), SwingConstants.CENTER);
			prevMonth = new BasicArrowButton(SwingConstants.WEST);
			nextMonth = new BasicArrowButton(SwingConstants.EAST);
			prevMonth.setActionCommand(PREVIOUS_MONTH);
			nextMonth.setActionCommand(NEXT_MONTH);
			prevMonth.addActionListener(actionListener);
			nextMonth.addActionListener(actionListener);
			monthPanel.add(prevMonth);
			monthPanel.add(monthName);
			monthPanel.add(nextMonth);
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return monthPanel;
	}

	private void updatePanels() {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				try {
					monthName.setText(monthFormat.valueToString(calendar
							.getTime()));
					yearName.setText(yearFormat.valueToString(calendar
							.getTime()));
					populateDayPanel();
				} catch (ParseException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				repaint();
			}
		});
	}

	public Date display() {
		return new Date();
	}

	/**
	 * This mouse listener will handle the selection/highlighting of days within
	 * the calendar.
	 * @author Eric Lundin
	 */
	private class CalendarMouseListener implements MouseListener {

		private static final String MONTH_LABEL_NAME = "Month Name";

		public void mouseClicked(final MouseEvent e) {
			String date = ((JLabel) ((JPanel) e.getSource()).getComponent(0))
					.getText();
			if (date.equals("C")) {
				spinner.setValue(null);
			}
			else {
				calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(date));
				try {
					spinner.setValue(formatter
							.valueToString(calendar.getTime()));
				} catch (ParseException e1) {
					e1.printStackTrace();
				}
			}
			setVisible(false);
		}

		public void mousePressed(final MouseEvent e) {
			// TODO Auto-generated method stub

		}

		public void mouseReleased(final MouseEvent e) {
			// TODO Auto-generated method stub

		}

		public void mouseEntered(final MouseEvent e) {
			Object eventSource = e.getSource();
			if (eventSource instanceof JPanel) {
				JPanel source = (JPanel) eventSource;
				source.setBackground(highlightColorBackground);
				source.getComponent(0).setForeground(Color.white);
			}
		}

		public void mouseExited(final MouseEvent e) {
			Object eventSource = e.getSource();
			if (eventSource instanceof JPanel) {
				JPanel source = (JPanel) eventSource;
				source.setBackground(normalColorBackground);
				source.getComponent(0).setForeground(normalColorText);
			}
		}
	}

	private class CalendarActionListener implements ActionListener {

		public void actionPerformed(final ActionEvent e) {
			if (e.getActionCommand().equals(NEXT_YEAR)) {
				calendar.roll(Calendar.YEAR, 1);
			}
			if (e.getActionCommand().equals(PREVIOUS_YEAR)) {
				calendar.roll(Calendar.YEAR, -1);
			}
			if (e.getActionCommand().equals(NEXT_MONTH)) {
				calendar.roll(Calendar.MONTH, 1);
			}
			if (e.getActionCommand().equals(PREVIOUS_MONTH)) {
				calendar.roll(Calendar.MONTH, -1);
			}
			updatePanels();
		}
	}

}
